﻿using CrawlerApp.Core.Entities;
using CrawlerApp.Core.Entities.BookAggregate;
using CrawlerApp.Extensions;
using CrawlerApp.Services.Interfaces;
using Flurl.Http;
using HtmlAgilityPack;
using Polly;
using Polly.Retry;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CrawlerApp.Services
{
    public class HtmlParseService : IHtmlPraseService
    {
        private readonly ILogger logger;
        private readonly RetryPolicy policy;

        public HtmlParseService(ILogger logger)
        {
            this.logger = logger;

            policy = Policy.Handle<FlurlHttpException>()
                    .Or<FlurlHttpTimeoutException>()
                    .Or<HttpRequestException>()
                    .Or<Exception>()
                    .WaitAndRetry(10, retryAttemp => TimeSpan.FromSeconds(3), (ex, time) =>
                    {
                        Trace.TraceWarning($"Flurl request exception ${time.TotalSeconds:n1}s retry request({ex.Message})");
                        logger.Warning(ex, "Flurl request exception {RetryTime}s retry request ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
                    });
        }

        public async Task<string> GetTextAsync(string url, string xPath)
        {
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if (response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var html = await response.GetStringAsync();

                    HtmlDocument document = new HtmlDocument();
                    document.LoadHtml(html);

                    return document.DocumentNode.SelectSingleNode(xPath).InnerText;
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }

            return null;
        }

        private IList<string> GetRegexMatchValue(string input, string pattern, RegexOptions options = RegexOptions.None)
        {
            List<string> list = new List<string>();
            var regex = Regex.Match(input, pattern, options);
            if (regex.Success && regex.Groups.Count > 1)
            {
                for (int i = 1; i < regex.Groups.Count; i++)
                {
                    list.Add(regex.Groups[i].Value.Trim());
                }
                return list;
            }
            return null;
        }

        private async Task<IEnumerable<string>> GetDownloadResourcesAsync(string url)
        {
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if(response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var html = await response.GetStringAsync();
                    HtmlDocument document = new HtmlDocument();
                    document.LoadHtml(html);

                    return document.DocumentNode
                        .SelectNodes("//span[@class='downfile']/a")
                        .Select(node => node.Attributes["href"]?.Value);
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            return null;
        }

        private async Task<IList<int>> GetAppRaiseAsync(int id)
        {
            var url = $"http://www.zxcs.me/content/plugins/cgz_xinqing/cgz_xinqing_action.php?action=mood&id={id}";
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if (response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var result = await response.GetStringAsync();
                    return result.Split(new char[] { ',' }).Select(it => it.ToInt()).ToList();
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            return null;
        }

        public async Task<Book> GetBookAsync(string url)
        {
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if (response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var html = await response.GetStringAsync();

                    HtmlDocument document = new HtmlDocument();
                    document.LoadHtml(html);

                    var temp = document.DocumentNode.SelectSingleNode("//div[@id='content']/h1").InnerText;
                    var list = GetRegexMatchValue(temp, @"《(.*)》（(.*)）[\s]?作者：(.*)");
                    if(list?.Count != 3)
                    {
                        logger.Error($"url:{url} not found book name");
                        return null;
                    }
                    var book = new Book("123", "234");
                    //var book = new Book(list[0]);
                    //book.BookInfo
                    //    .SetVersion(list[1])
                    //    .SetAuthor(list[2]);

                    //var node = document.DocumentNode.SelectSingleNode("//div[@id='content']/p[@class='date']");
                    //list = GetRegexMatchValue(node.InnerText, @"本书由&nbsp;(.*)&nbsp;于&nbsp;大约 (.*) (.*)前");
                    //if (list?.Count == 3)
                    //{
                    //    book.BookInfo
                    //        .SetPublisher(list[0])
                    //        .SetPublishDate($"{list[1]}{list[2]}前");
                    //}

                    //var nodes = document.DocumentNode.SelectNodes("//div[@id='content']/p[@class='date']/a");
                    //if (nodes?.Count > 2)
                    //{
                    //    book.BookInfo.SetCategory(nodes[1].InnerText, nodes[1].Attributes["href"]?.Value);
                    //    for(int i = 2; i < nodes.Count; i++)
                    //    {
                    //        book.BookInfo.AddTag(new Tag(nodes[i].InnerHtml, nodes[i].Attributes["href"]?.Value));
                    //    }
                    //}
                    //node = document.DocumentNode.SelectSingleNode("//div[@id='content']");
                    //var doc = new HtmlDocument();
                    //doc.LoadHtml(node.OuterHtml);
                    //node = doc.DocumentNode.SelectSingleNode("//img");
                    //book.BookInfo.SetCover(doc.DocumentNode.SelectSingleNode("//img").Attributes["src"]?.Value);

                    //nodes = document.DocumentNode.SelectNodes("//p[@class='fileinfo']/span");
                    //if(nodes?.Count == 2)
                    //{
                    //    book.BookInfo.SetExtensionName(nodes[0].InnerText.Replace("格式：", ""));
                    //    book.BookInfo.SetSize(nodes[1].InnerText.Replace("大小：", ""));
                    //}

                    //temp = document.DocumentNode.SelectSingleNode("//div[@id='content']")?.InnerText.Replace("\r\n", "\n").Replace("&nbsp;", "");
                    //list = GetRegexMatchValue(temp, "[【内容简介】|内容简介]+：\\s*\n*([^《]*)");
                    //if(list?.Count == 1)
                    //{
                    //    book.BookInfo.SetIntro(list[0]);
                    //}
                    //else
                    //{
                    //    list = GetRegexMatchValue(temp, "[【内容简介】|内容简介]+：\\s*\n*([^《]*)");
                    //}

                    //var downloadResourceUrl = await GetDownloadResourcesAsync(document.DocumentNode.SelectSingleNode("//div[@class='down_2']/a").Attributes["href"]?.Value);
                    //book.AddRangeDownloadResource(downloadResourceUrl.ToList());

                    //var id = GetRegexMatchValue(url, @"(\d+)$")[0].ToInt();
                    //var appRaise = await GetAppRaiseAsync(id);
                    //if(appRaise.Count == 5)
                    //{
                    //    book.SetAppRaise(appRaise[0], appRaise[1], appRaise[2], appRaise[3], appRaise[4]);
                    //}

                    //book.SetHref(url);

                    return book;
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }

            return null;
        }

        public async Task<string> GetHrefAsync(string url,string xPath)
        {
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if (response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var html = await response.GetStringAsync();

                    HtmlDocument document = new HtmlDocument();
                    document.LoadHtml(html);

                    return document.DocumentNode.SelectSingleNode(xPath).Attributes["href"]?.Value;
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }

            return null;

        }

        public async Task<IReadOnlyCollection<T>> GetSpecifyCollectionAsync<T>(string url, string xPath, Expression<Func<HtmlNode, T>> selector, Expression<Func<HtmlNode, bool>> predicate = null)
        {
            try
            {
                var response = policy.Execute(() => url.GetAsync().Result);
                if (response.ResponseMessage.IsSuccessStatusCode && response.StatusCode == 200)
                {
                    var html = await response.GetStringAsync();

                    HtmlDocument document = new HtmlDocument();
                    document.LoadHtml(html);

                    var nodes = document.DocumentNode.SelectNodes(xPath).AsQueryable();
                    if (predicate != null)
                    {
                        nodes = nodes.Where(predicate);
                    }
                    return nodes
                        .Select(selector)
                        .ToList()
                        .AsReadOnly();
                }
            }
            catch (FlurlParsingException ex)
            {
                Trace.TraceError($"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"flurl parse fail url:{url}{Environment.NewLine}exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }
            catch (Exception ex)
            {
                Trace.TraceError($"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
                logger.Error(ex, $"unknow exception:{ex.Message}{Environment.NewLine}stack trace:{ex.StackTrace}");
            }

            return null;
        }
    }
}
